/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.lifecycle.base;

import org.proteusframework.core.api.model.INamespace;
import org.proteusframework.core.base.DisplayableName;
import org.proteusframework.core.util.Assert;
import org.proteusframework.platformservice.AbstractServicePlugin;
import org.proteusframework.platformservice.lifecycle.api.*;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;

public abstract class AbstractLifecycleService extends AbstractServicePlugin implements ILifecycleService
{
    private static final Logger logger = Logger.getLogger(AbstractLifecycleService.class.getCanonicalName());

    private final Object lock = new Object();

    private List<ILifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<ILifecycleListener>();

    private Map<Class<? extends EventObject>, Class<? extends EventListener>> customEventsMap = new HashMap<Class<? extends EventObject>, Class<? extends EventListener>>();

    private Map<Class<? extends EventObject>, Method> callbackMap = new HashMap<Class<? extends EventObject>, Method>();

    private Map<Class<? extends EventObject>, List<EventListener>> customEventListeners = new HashMap<Class<? extends EventObject>, List<EventListener>>();

    protected AbstractLifecycleService(INamespace namespace, Class lifecycleClass)
    {
        super(new DisplayableName(namespace), lifecycleClass);
    }

    @Override
    public final <T extends EventObject, S extends EventListener> void registerCustomEventType(Class<T> customEventType, Class<S> eventListenerType, Method listenerCallbackMethod)
    {
        boolean match = false;

        synchronized (lock)
        {
            if (customEventsMap.containsKey(customEventType))
            {
                logger.info("Custom event " + customEventType.getCanonicalName() + " was already registered with the lifecycle service");
                return;
            }

            Method[] methods = eventListenerType.getMethods();
            for (Method method : methods)
            {
                if (method.getName().equals(listenerCallbackMethod.getName()))
                {
                    Class[] params = method.getParameterTypes();
                    if (params.length == 1)
                    {
                        if (params[0] == customEventType)
                        {
                            match = true;
                        } else
                        {
                            throw new IllegalArgumentException("Listener call back method exists, but the parameter didn't match the customEventType being registered");
                        }
                    } else
                    {
                        throw new IllegalArgumentException("Listener call back method exists, but takes too many parameters");
                    }
                }
            }

            if (match)
            {
                callbackMap.put(customEventType, listenerCallbackMethod);
                customEventsMap.put(customEventType, eventListenerType);
                customEventListeners.put(customEventType, new CopyOnWriteArrayList<EventListener>());
            } else
            {
                throw new IllegalArgumentException("No matching method found on the eventListenerType");
            }
        }

        //
        // Notify anyone listening that a new custom event has been registered and is ready to accept subscribers
        LifecycleCustomEventRegistration event = new LifecycleCustomEventRegistration(customEventType);
        fireLifecycleEvent(event);
    }

    @Override
    public final <T extends EventObject> void addCustomListener(Class<T> customEventType, EventListener listener)
    {
        Assert.parameterNotNull(customEventType, "Parameter 'customEventType' must not be null");
        Assert.parameterNotNull(listener, "Parameter 'listener' must not be null");

        synchronized (lock)
        {
            if (!customEventsMap.containsKey(customEventType))
            {
                throw new IllegalArgumentException("No matching custom event type is registered");
            }

            Class<? extends EventListener> mappedListener = customEventsMap.get(customEventType);
            if (!mappedListener.isAssignableFrom(listener.getClass()))
            {
                throw new IllegalArgumentException("Parameter 'listener' is not assignable from " + customEventsMap.get(customEventType).getCanonicalName());
            }

            customEventListeners.get(customEventType).add(listener);
        }
    }

    @Override
    public final void addCustomListener(EventListener listener)
    {
        Assert.parameterNotNull(listener, "Parameter 'listener' must not be null");
        boolean match = false;
        Class<? extends EventObject> matchKey = null;

        synchronized (lock)
        {
            for (Class<? extends EventObject> eventObject : customEventsMap.keySet())
            {
                Class<? extends EventListener> mappedListener = customEventsMap.get(eventObject);
                if (mappedListener.isAssignableFrom(listener.getClass()))
                {
                    matchKey = eventObject;
                    match = true;
                    break;
                }
            }

            if (match)
            {
                customEventListeners.get(matchKey).add(listener);
            } else
            {
                throw new IllegalArgumentException("No registered custom event class maps to the interface defined by the listener");
            }
        }
    }

    @Override
    public final <T extends EventObject> void fireCustomEvent(Class<T> customEventType, T event)
    {
        Assert.parameterNotNull(customEventType, "Parameter 'customEventType' must not be null");
        Assert.parameterNotNull(event, "Parameter 'event' must not be null");

        Method method = callbackMap.get(customEventType);

        if (method == null)
        {
            throw new IllegalStateException("Missing customEventType method association");
        }

        Object[] listeners = customEventListeners.get(customEventType).toArray();
        for (Object listener : listeners)
        {
            try
            {
                method.invoke(listener, event);
            } catch (IllegalAccessException e)
            {
                e.printStackTrace();
            } catch (InvocationTargetException e)
            {
                e.printStackTrace();
            }
        }
    }

    @Override
    public final <T extends EventObject> void fireCustomEvent(T event)
    {
        for (Class<? extends EventObject> eventObject : customEventsMap.keySet())
        {
            if (event.getClass().equals(eventObject))
            {
                Method method = callbackMap.get(eventObject);

                if (method == null)
                {
                    throw new IllegalStateException("Missing customEventType method association");
                }

                Object[] listeners = customEventListeners.get(eventObject).toArray();
                for (Object listener : listeners)
                {
                    try
                    {
                        method.invoke(listener, event);
                    } catch (IllegalAccessException e)
                    {
                        e.printStackTrace();
                    } catch (InvocationTargetException e)
                    {
                        e.printStackTrace();
                    }
                }

                break;
            }
        }
    }

    @Override
    public final <T extends EventObject> void removeCustomListener(Class<T> customEventType, EventListener listener)
    {
        if (null != customEventType && null != listener)
        {
            customEventListeners.get(customEventType).remove(listener);
        }
    }

    @Override
    public <T extends EventObject> boolean isCustomEventRegistered(Class<T> customEventType)
    {
        return customEventListeners.containsKey(customEventType);
    }

    @Override
    public final void removeCustomListener(EventListener listener)
    {
        boolean match = false;
        Class<? extends EventObject> matchKey = null;

        for (Class<? extends EventObject> eventObject : customEventsMap.keySet())
        {
            if (listener.getClass().isAssignableFrom(customEventsMap.get(eventObject)))
            {
                matchKey = eventObject;
                match = true;
                break;
            }
        }

        if (match)
        {
            customEventListeners.get(matchKey).remove(listener);
        } else
        {
            throw new IllegalArgumentException("No registered custom event class maps to the interface defined by the listener");
        }
    }

    @Override
    public final <T extends EventObject> void unregisterCustomEventType(Class<T> customEventType)
    {
        synchronized (lock)
        {
            customEventListeners.remove(customEventType);
            customEventsMap.remove(customEventType);
            callbackMap.remove(customEventType);
        }
    }

    @Override
    public final void addLifecycleListener(ILifecycleListener listener)
    {
        Assert.parameterNotNull(listener, "Parameter 'listener' must not be null");
        lifecycleListeners.add(listener);
    }

    @Override
    public final void removeLifecycleListener(ILifecycleListener listener)
    {
        if (null != listener)
        {
            lifecycleListeners.remove(listener);
        }
    }

    @Override
    public final void fireLifecycleEvent(LifecycleServiceIntentionEvent event)
    {
        for (ILifecycleListener listener : lifecycleListeners)
        {
            try
            {

                listener.onLifecycleEvent(event);

            } catch (Exception e)
            {
                StringBuilder buf = new StringBuilder("Exception notifying ")
                        .append(listener.getFamily())
                        .append(" ")
                        .append(listener.getId())
                        .append(" of lifecycle service intention event.\n\n")
                        .append(e.getMessage());

                logger.warning(buf.toString());
            }
        }
    }

    @Override
    public final void fireLifecycleEvent(LifecycleActivityEvent event)
    {
        for (ILifecycleListener listener : lifecycleListeners)
        {
            try
            {

                listener.onLifecycleEvent(event);

            } catch (Exception e)
            {
                StringBuilder buf = new StringBuilder("Exception notifying ")
                        .append(listener.getFamily())
                        .append(" ")
                        .append(listener.getId())
                        .append(" of lifecycle activity event.\n\n")
                        .append(e.getMessage());

                logger.warning(buf.toString());
            }
        }
    }

    @Override
    public void fireLifecycleEvent(LifecycleCustomEventRegistration event)
    {
        for (ILifecycleListener listener : lifecycleListeners)
        {
            if (IExtendedLifecycleListener.class.isAssignableFrom(listener.getClass()))
            {
                IExtendedLifecycleListener eventListener
                        = IExtendedLifecycleListener.class.cast(listener);

                try
                {

                    eventListener.onLifecycleEvent(event);

                } catch (Exception e)
                {
                    StringBuilder buf = new StringBuilder("Exception notifying ")
                            .append(listener.getFamily())
                            .append(" ")
                            .append(listener.getId())
                            .append(" of lifecycle custom event registration.\n\n")
                            .append(e.getMessage());

                    logger.warning(buf.toString());
                }
            }
        }
    }
}

